// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <pollux/common/base/status.h>
#include <pollux/functions/macros.h>
#include <pollux/functions/prestosql/types/bing_tile_type.h>

namespace kumo::pollux::functions {
    template<typename T>
    struct BingTileFunction {
        POLLUX_DEFINE_FUNCTION_TYPES(T);

        MELON_ALWAYS_INLINE Status call(
            out_type<BingTile> &result,
            const arg_type<int32_t> &x,
            const arg_type<int32_t> &y,
            const arg_type<int8_t> &zoom) {
            if (MELON_UNLIKELY(x < 0)) {
                return Status::UserError(
                    fmt::format("Bing tile X coordinate {} cannot be negative", x));
            }
            if (MELON_UNLIKELY(y < 0)) {
                return Status::UserError(
                    fmt::format("Bing tile Y coordinate {} cannot be negative", y));
            }
            if (MELON_UNLIKELY(zoom < 0)) {
                return Status::UserError(
                    fmt::format("Bing tile zoom {} cannot be negative", zoom));
            }

            uint64_t tile = BingTileType::bingTileCoordsToInt(
                static_cast<uint32_t>(x),
                static_cast<uint32_t>(y),
                static_cast<uint8_t>(zoom));
            if (MELON_UNLIKELY(!BingTileType::isBingTileIntValid(tile))) {
                std::optional<std::string> reason =
                        BingTileType::bingTileInvalidReason(tile);
                if (reason.has_value()) {
                    return Status::UserError(reason.value());
                } else {
                    return Status::UnknownError(fmt::format(
                        "Pollux Error constructing BingTile from x {} y {} zoom {}; please report this.",
                        x,
                        y,
                        zoom));
                }
            }
            result = tile;
            return Status::OK();
        }

        MELON_ALWAYS_INLINE Status
        call(out_type<BingTile> &result, const arg_type<Varchar> &quadKey) {
            melon::Expected<uint64_t, std::string> tile =
                    BingTileType::bingTileFromQuadKey(std::string_view(quadKey));
            if (tile.hasError()) {
                return Status::UserError(tile.error());
            }
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tile.value()));
            result = tile.value();
            return Status::OK();
        }
    };

    template<typename T>
    struct BingTileZoomLevelFunction {
        POLLUX_DEFINE_FUNCTION_TYPES(T);

        MELON_ALWAYS_INLINE void call(
            out_type<int8_t> &result,
            const arg_type<BingTile> &tile) {
            uint64_t tileInt = tile;
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tileInt));
            result = BingTileType::bingTileZoom(tileInt);
        }
    };

    template<typename T>
    struct BingTileCoordinatesFunction {
        POLLUX_DEFINE_FUNCTION_TYPES(T);

        MELON_ALWAYS_INLINE void call(
            out_type<Row<int32_t, int32_t> > &result,
            const arg_type<BingTile> &tile) {
            uint64_t tileInt = tile;
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tileInt));
            result = std::make_tuple(
                static_cast<int32_t>(BingTileType::bingTileX(tileInt)),
                static_cast<int32_t>(BingTileType::bingTileY(tileInt)));
        }
    };

    template<typename T>
    struct BingTileParentFunction {
        POLLUX_DEFINE_FUNCTION_TYPES(T);

        MELON_ALWAYS_INLINE Status
        call(out_type<BingTile> &result, const arg_type<BingTile> &tile) {
            uint64_t tileInt = tile;
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tileInt));
            uint8_t tileZoom = BingTileType::bingTileZoom(tile);
            if (MELON_UNLIKELY(tileZoom == 0)) {
                return Status::UserError(
                    fmt::format("Cannot call bing_tile_parent on zoom 0 tile"));
            }
            auto parent = BingTileType::bingTileParent(tileInt, tileZoom - 1);
            if (MELON_UNLIKELY(parent.hasError())) {
                return Status::UserError(parent.error());
            }
            result = parent.value();
            return Status::OK();
        }

        MELON_ALWAYS_INLINE Status call(
            out_type<BingTile> &result,
            const arg_type<BingTile> &tile,
            const arg_type<int8_t> &parentZoom) {
            uint64_t tileInt = tile;
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tileInt));
            if (MELON_UNLIKELY(parentZoom < 0)) {
                return Status::UserError(
                    fmt::format("Cannot call bing_tile_parent with negative zoom"));
            }
            auto parent = BingTileType::bingTileParent(tileInt, parentZoom);
            if (MELON_UNLIKELY(parent.hasError())) {
                return Status::UserError(parent.error());
            }
            result = parent.value();
            return Status::OK();
        }
    };

    template<typename T>
    struct BingTileChildrenFunction {
        POLLUX_DEFINE_FUNCTION_TYPES(T);

        MELON_ALWAYS_INLINE Status
        call(out_type<Array<BingTile> > &result, const arg_type<BingTile> &tile) {
            uint64_t tileInt = tile;
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tileInt));
            uint8_t tileZoom = BingTileType::bingTileZoom(tile);
            if (MELON_UNLIKELY(tileZoom >= BingTileType::kBingTileMaxZoomLevel)) {
                return Status::UserError(
                    fmt::format("Cannot call bing_tile_children on zoom 23 tile"));
            }
            auto childrenRes = BingTileType::bingTileChildren(tileInt, tileZoom + 1);
            if (MELON_UNLIKELY(childrenRes.hasError())) {
                return Status::UserError(childrenRes.error());
            }
            std::vector<uint64_t> children = childrenRes.value();
            result.reserve(children.size());
            result.add_items(children);
            return Status::OK();
        }

        MELON_ALWAYS_INLINE Status call(
            out_type<Array<BingTile> > &result,
            const arg_type<BingTile> &tile,
            const arg_type<int8_t> &childZoom) {
            uint64_t tileInt = tile;
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tileInt));
            if (MELON_UNLIKELY(childZoom < 0)) {
                return Status::UserError(
                    fmt::format("Cannot call bing_tile_children with negative zoom"));
            }
            auto childrenRes = BingTileType::bingTileChildren(tileInt, childZoom);
            if (MELON_UNLIKELY(childrenRes.hasError())) {
                return Status::UserError(childrenRes.error());
            }
            std::vector<uint64_t> children = childrenRes.value();
            result.reserve(children.size());
            result.add_items(children);
            return Status::OK();
        }
    };

    template<typename T>
    struct BingTileToQuadKeyFunction {
        POLLUX_DEFINE_FUNCTION_TYPES(T);

        MELON_ALWAYS_INLINE void call(
            out_type<Varchar> &result,
            const arg_type<BingTile> &tile) {
            uint64_t tileInt = static_cast<uint64_t>(tile);
            POLLUX_DCHECK(BingTileType::isBingTileIntValid(tileInt));
            result = BingTileType::bingTileToQuadKey(tileInt);
        }
    };
} // namespace kumo::pollux::functions
